interface IWrapperBinds {
  style: {
    height: string
  }
}

interface IListCell<T> {
  binds: {
    style: {
      height: string,
    }
  }
  value: T,
  index: number
}

interface IOptions {
  elName?: string,
  offset?: number,
  cellOverflow?: string
}



const defaultOptions = {
  elName: "containerEl",
  offset: 5,
  cellOverflow: "auto"
}

import { onMounted, Ref, ref, shallowRef, watch, reactive, onUnmounted } from "vue"

export function useVirtualList<T = any>(_originalList: T[] | Ref<T[]>, itemHeight: number | ((index: number, item: T) => number), _options: IOptions = {}) {
  const options = { ...defaultOptions, ..._options };
  if (options.offset < 1) {
    console.warn("options.offset 不可小于1");
    options.offset = 1;
  }

  let originalList = Array.isArray(_originalList) ? _originalList : _originalList.value;

  if (!Array.isArray(_originalList)) {
    let stop = watch(_originalList, reBuild);
    onUnmounted(stop)
  }

  // 元素高度的缓存
  let itemHeightCache = [] as number[];
  const containerEl = ref(null);
  let container: HTMLElement;

  let startIndex = 0; // 开始展示的节点;
  let endIndex = 0; // 结束展示的节点
  let showList = shallowRef([] as IListCell<T>[]); // 具体展示的

  onMounted(() => {
    container = containerEl.value as unknown as HTMLElement;
    _setStartAndEnd();
    _setShowList();
  })

  const wrapperBinds: IWrapperBinds = reactive({
    style: {
      height: `${_getTotalHeight()}px`,
    }
  });
  const containerBinds = {
    onScroll: () => {
      _setStartAndEnd();
      _setShowList();
    },
    ref: options.elName
  }

  function reBuild() {
    itemHeightCache.length = 0;
    originalList = Array.isArray(_originalList) ? _originalList : _originalList.value;
    wrapperBinds.style.height = `${_getTotalHeight()}px`
    _setStartAndEnd();
    _setShowList();
  }

  function _getCellBinds(index: number) {
    // 每个元素都应该下移被卷起的元素的高度和
    let transformY: number;
    let height: number;
    if (typeof itemHeight === "number") {
      height = itemHeight;
      transformY = startIndex * itemHeight
    } else {
      height = _getItemHeight(index);
      transformY = originalList.slice(0, startIndex).reduce((t, _, i) => {
        return t + _getItemHeight(i);
      }, 0)
    }
    return {
      style: {
        height: `${height}px`,
        transform: `translateY(${transformY}px)`,
        overflow: options.cellOverflow
      }
    }
  }

  function _getItemHeight(index: number) {
    if (typeof itemHeight === "number") {
      return itemHeight
    }
    if (itemHeightCache[index] !== undefined) {
      return itemHeightCache[index]
    } else {
      const height = itemHeight(index, originalList[index]);
      itemHeightCache[index] = height;
      return height;
    }
  }

  function _getTotalHeight() {
    if (typeof itemHeight === "number") {
      return originalList.length * itemHeight
    } else {
      return originalList.reduce((t, _, i) => {
        return t + _getItemHeight(i);
      }, 0)
    }
  }

  function _setStartAndEnd() {
    let _start, _end: number

    if (typeof itemHeight === "number") {
      _start = Math.floor(container.scrollTop / itemHeight) - options.offset;
      _start = Math.max(0, _start);
      _end = _start + Math.floor(container.clientHeight / itemHeight) + options.offset * 2;
    } else {
      let _startTotalHeight = 0;
      let _startCountIndex = 0;
      while (true) {
        if (_startCountIndex >= originalList.length - 1) {
          break
        }
        _startTotalHeight += _getItemHeight(_startCountIndex);
        if (_startTotalHeight >= container.scrollTop) {
          break
        }
        _startCountIndex++;
      }
      _start = _startCountIndex - options.offset;
      _start = Math.max(0, _start);

      let _showTotalHeight = 0;
      let _showCountIndex = _start + 1;
      // 开始计算需要几个元素填满
      while (true) {
        if (_showCountIndex >= originalList.length - 1) {
          break
        }
        _showTotalHeight += _getItemHeight(_showCountIndex);
        if (_showTotalHeight >= container.offsetHeight) {
          break
        }
        _showCountIndex++;
      }
      _end = _showCountIndex + options.offset * 2;
    }
    startIndex = _start;
    endIndex = _end;
  }

  function _setShowList() {
    showList.value = originalList.slice(startIndex, endIndex).map((value, _index) => ({
      binds: _getCellBinds(_index + startIndex),
      index: _index + startIndex,
      value,
    }));
  }

  return { wrapperBinds, containerBinds, showList, reBuild, [options.elName]: containerEl, containerEl }
}
export default useVirtualList